'''
This module defines the background variables and functions
Each time when the main window opens, the initializer will call functions here
'''

import os
import re

from PyQt4 import QtGui, QtCore

from internal import *

# internal settings are used when the reading of default settings fails
AA_PROPERTIES_INTERNAL = constructAAPropertiesInternal()
AA_SETTINGS_INTERNAL = constructAASettingsInternal()
PROPERTY_COLORS_INTERNAL = constructPropertyColorsInternal()
PLOT_PARAMETERS_INTERNAL = constructPlotParametersInternal()

# settings files are saved in current directory as other program files
FILE_SETTINGS_DEFAULT = './settings_default.xml'
FILE_SETTINGS_RECENT = './settings_recent.xml'
SHADE_COLORS_RECENT = './shade_colors_recent.txt'


def getSettingsDefault():
    """
    This function is to setup the default settings
    First, the function tries to read the default setting file if there is one
    If there is no default setting file or the file's data is not valid, 
        the default settings will be set to internal settings
    """
    settings = {}
    settings['aa properties'] = AA_PROPERTIES_INTERNAL
    settings['property type definition'] = AA_SETTINGS_INTERNAL
    settings['property color mapping'] = PROPERTY_COLORS_INTERNAL
    
    if os.path.exists(FILE_SETTINGS_DEFAULT):
        settingsFromFile = readSettingsFile(FILE_SETTINGS_DEFAULT)
        if not validateSettings(settingsFromFile):
            
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Invalid Settings')
            messageBox.setText('The default setting file is invalid. '
                                        'You may close the program and edit '
                                        'the default setting file or '
                                        'continue to use internal settings.')
            
            messageBox.exec_()
            
        else:
            settings = settingsFromFile
    
    return settings


def getSettingsRecent():
    """
    This function is to setup the settings most recently used
    If there is no such settings saved or the saved data is not invalid,
        the most recent settings will be set to None
    """
    if os.path.exists(FILE_SETTINGS_RECENT):
        settings = readSettingsFile(FILE_SETTINGS_RECENT)
        if not validateSettings(settings):
            settings = None
    else:
        settings = None
    
    return settings


def getPlotParametersDefault():
    """
    This function is to setup the parameters for image drawing
    """    
    return PLOT_PARAMETERS_INTERNAL


def getShadeColorsRecent():
    """
    This function is to setup the shade colors most recently used
    """
    shadeColorsRecent = []
    if os.path.exists(SHADE_COLORS_RECENT):
        with open(SHADE_COLORS_RECENT) as fin:
            for eachline in fin:
                try:
                    color = QtGui.QColor(eachline.strip())
                    shadeColorsRecent.append(color)
                except Exception():
                    continue

    return shadeColorsRecent


def putShadeColorsRecent(colorList):
    """
    This function is to save the recently used shade colors
    """
    fout = open(SHADE_COLORS_RECENT, 'w')
    for color in colorList:
        colorString = '#' + hex(color.rgb())[4:].upper()
        fout.write(colorString)
        fout.write('\n')
    fout.close()


def readSettingsFile(settingsFile):
    """
    This function is to read and construct the settings dictionary from a file
    """
    settings  = {}
    
    with open(settingsFile) as fin:
        contents = ''.join([eachline.strip().lower() 
                            for eachline in fin.readlines()])
    
    # patterns to locate the 3 different sections
    propStr = r'<aa properties>(.*)</aa properties>'
    propTypeStr = r'<property type definition>(.*)</property type definition>'
    propColorStr = r'<property color mapping>(.*)</property color mapping>'
    
    aaPropertiesMatch = re.search(propStr, contents)
    propertyTypeMatch = re.search(propTypeStr, contents)
    propertyColorMatch = re.search(propColorStr, contents)
    
    if aaPropertiesMatch:
        aaProperties = aaPropertiesMatch.group(1).strip()
        settings['aa properties'] = {}
    else:
        aaProperties = None
        settings['aa properties'] = None
    
    if propertyTypeMatch:
        propertyType = propertyTypeMatch.group(1).strip()
        settings['property type definition'] = {}
    else:
        propertyType = None
        settings['property type definition'] = None
    
    if propertyColorMatch:
        propertyColor = propertyColorMatch.group(1).strip()
        settings['property color mapping'] = {}
    else:
        propertyColor = None
        settings['property color mapping'] = None
    
    if aaProperties:
        aaPropertyMatches = re.findall(r'<(.)>(.*)</\1>', aaProperties)
        for eachMatch in aaPropertyMatches:
            aa = eachMatch[0].upper()
            aaProperty = eachMatch[1]
            settings['aa properties'][aa] = {}
            
            propertyValueMatches = re.findall(r'<(.+)>(.*)</\1>', aaProperty)
            for eachProperty in propertyValueMatches:
                propertyName = eachProperty[0]
                try:
                    propertyValue = float(eachProperty[1].strip())
                except ValueError:
                    propertyValue = None
                
                settings['aa properties'][aa][propertyName] = propertyValue
    
    if propertyType:
        propertyTypeMatches = re.findall(r'<(.+)>(.*)</\1>', propertyType)
        for eachMatch in propertyTypeMatches:
            property = eachMatch[0]
            typeAndRange = eachMatch[1]
            settings['property type definition'][property] = {}
            
            typeRangeMatches = re.findall(r'<(.+)>(.*)</\1>', typeAndRange)
            for eachType in typeRangeMatches:
                typeName = eachType[0]
                typeRange = eachType[1].strip()
                
                try:
                    assert typeRange[0] == '[' and typeRange[-1] == ')'
                    rangeBounds = typeRange[1:-1].split(',')
                    assert len(rangeBounds) == 2
                    
                    rangeLB = float(rangeBounds[0])
                    rangeUB = float(rangeBounds[1])
                    rangeInterval = (rangeLB, rangeUB)
                except Exception():
                    rangeInterval = None
                
                settings['property type definition'][property][typeName] = \
                                                rangeInterval
    
    if propertyColor:
        propertyColorMatches = re.findall(r'<(.+)>(.*)</\1>', propertyColor)
        for eachMatch in propertyColorMatches:
            property = eachMatch[0]
            typeAndColor = eachMatch[1]
            settings['property color mapping'][property] = {}
            
            typeColorMatches = re.findall(r'<(.+)>(.*)</\1>', typeAndColor)
            for eachType in typeColorMatches:
                typeName = eachType[0]
                if property == 'identity':
                    typeName = typeName.upper()
                
                typeColor = eachType[1].strip()
                try:
                    color = QtGui.QColor(typeColor)
                except Exception():
                    color = None
                
                settings['property color mapping'][property][typeName] = color
                
            if property != 'identity':
                settings['property color mapping'][property]['undefined'] = \
                                        QtGui.QColor(QtCore.Qt.white)
    
    return settings


def validateSettings(settings):
    """
    This function is to validate the settings read from a file
    The settings must be consistent in various aspectives:
        amino acid types, property types, property categories, etc.
    """
    
    # amino acids from property section and color mapping section 
    # must be the same as AA list
    aaFromAAProperties = set(settings['aa properties'].keys())
    aaFromIdentityColorMapping = set(settings['property color mapping']\
                                     ['identity'].keys())
    if aaFromAAProperties != aaFromIdentityColorMapping or \
                            aaFromAAProperties != set(AA_LIST):
        # print("Error #1")
        return False
    
    # properties from all three sections must be consistent
    propertiesFromAAProperties = settings['aa properties']['A'].keys()
    propertiesFromPropertyType = settings['property type definition'].keys()
    propertiesFromPropertyColor = set([property for property in 
                                       settings['property color mapping'].keys() 
                                       if property != 'identity'])    
    if propertiesFromPropertyType != propertiesFromPropertyColor or \
                    propertiesFromPropertyType != propertiesFromAAProperties:
        # print("Error #2")
        return False
    
    # ensures that default properties must exist
    defaultProperties = ['size', 'charge', 'polarity', 'hydropathy']
    for property in defaultProperties:
        if property not in propertiesFromAAProperties:
            # print("Error #3")
            return False

    # properties for all amino acids must be consistent
    for aa in AA_LIST[1:]:
        propertiesFromOtherAAProperties = settings['aa properties'][aa].keys()
        if propertiesFromOtherAAProperties != propertiesFromAAProperties:
            # print("Error #4")
            return False
    
    # property categories for each property must be consistent
    for eachProperty in propertiesFromPropertyType:
        propertyTypesFromDefinition = set(settings['property type definition']
                                                        [eachProperty].keys())
        propertyTypesFromColorMapping = set(settings['property color mapping']
                                                            [eachProperty].keys())
        
        if 'undefined' in propertyTypesFromColorMapping:
            propertyTypesFromColorMapping.remove('undefined')
        
        if propertyTypesFromDefinition != propertyTypesFromColorMapping:
            # print("Error #5")
            return False
    
    # each property cateogery interval must be valid
    # the intervals for a certain category cannot overlap
    for eachProperty in propertiesFromPropertyType:
        rangeIntervals = []
        for eachType in settings['property type definition'][eachProperty]:
            rangeInterval = settings['property type definition']\
                                    [eachProperty][eachType]
            if rangeInterval[0] > rangeInterval[1]:
                # print("Error #6")
                return False
            
            rangeIntervals.append(rangeInterval)
        
        # sort the intervals to test overlap
        rangeIntervals.sort()
        for i in range(1, len(rangeIntervals)):
            if rangeIntervals[i][0] < rangeIntervals[i-1][1]:
                # print("Error #7")
                return False
    
    return True
